package com.nys.conci.base.utilitario;

import com.nys.conci.backend.entidad.InformacionUsuario;
import com.nys.conci.backend.modelo.EstadoSesion;
import com.nys.conci.base.excepcion.ExcepcionManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 *
 * @author Adam M. Gamboa González
 * @since 17 de Febrero de 2012
 */
public class SesionUtil {
    
    //Clases que necesaria para la extraccion de las session con reflection
    private static final String MEMORY_SESSION_DATA = "glassfish.servlet.internal.session.MemorySessionData";
    private static final String MEMORY_SESSION_CONTEXT = "glassfish.servlet.internal.session.MemorySessionContext";
    //Atributo de session del usuario
    private static final String ATRIBUTO_SESION_INFO_USUARIO = "INFO_USUARIO";
    private static final String ATRIBUTO_SESION_IDENTIDAD = "org.jboss.weld.context.http.HttpSessionContext#org.jboss.weld.bean-SistemaBienesInmuebles_WAR-ManagedBean-class com.nys.conci.backend.entidad.Identidad";    
    private static final String ATRIBUTO_SESION_IDENTIDAD_CLASS = "com.nys.conci.backend.entidad.Identidad";    
    private static final String REDIRECT = "/xhtml/logout.xhtml";

    public enum VariablesSesion {

        INFO_USUARIO;
    }
    
    
    /**
     * Remueve una variable de la session
     * @param llave Llave del objeto a eliminar.
     */
    public static void removerDeSesion(VariablesSesion llave){
        JSFUtil.removerDeSesion(llave.toString());
    }
    
    /**
     * Metodo para setear/guardar variables en session.
     * @param llave Identificador/llave del objeto a guardar
     * @param objeto Objeto a guardar
     */
    public static void guardarEnSesion(VariablesSesion llave, Object objeto){
        JSFUtil.guardarEnSesion(llave.toString(), objeto);
    }
    
    /**
     * Metodo para obtener variables de session por JSF.
     * Si un objeto no existe en session, entonces retorna null.
     * @param llave Llave de objeto en sesion
     * @return Objeto obtenido.
     */
    public static Object obtenerDeSesion(VariablesSesion llave){
       return JSFUtil.obtenerDeSesion(llave.toString());
    }
    
    
/**
     * Obtiene las sesiones abiertas en el servidor
     *
     * @param facesContext Recibe como parametro el contexto de la aplicacion
     * @return Retorna una lista de usuarios que se encuentran identificados en
     * la aplicacion.
     */
    public static List<SessionWrapper> obtenerSesionesAbiertas(FacesContext facesContext) {
        Long lastRequestTime;
        SessionWrapper sessionWrapper;
        List<SessionWrapper> listaSesionesAbiertas = new ArrayList<SessionWrapper>();
        try {
            Class memorySessionData = Class.forName(MEMORY_SESSION_DATA);
            Class memorySessionContext = Class.forName(MEMORY_SESSION_CONTEXT);

            HttpSession httpSession = (HttpSession) facesContext.getExternalContext().getSession(true);

            Method getOpenSessions = memorySessionContext.getMethod("getOpenSessions");
            Hashtable<String, Object> sessionAbiertas = (Hashtable<String, Object>) getOpenSessions.invoke(httpSession.getSessionContext());

            Iterator it = sessionAbiertas.entrySet().iterator();

            while (it.hasNext()) {
                Map.Entry ent = (Map.Entry) it.next();
                if (memorySessionData.isInstance(ent.getValue())) {
                    Method getLastAccessedTime = memorySessionData.getMethod("getLastAccessedTime");
                    lastRequestTime = (new Date().getTime() - (Long) getLastAccessedTime.invoke(ent.getValue())) / 1000;
                    Method getAttribute = memorySessionData.getMethod("getAttribute", String.class);
                    if (getAttribute.invoke(ent.getValue(), ATRIBUTO_SESION_INFO_USUARIO) instanceof InformacionUsuario) {
                        sessionWrapper = (SessionWrapper) getAttribute.invoke(ent.getValue(), ATRIBUTO_SESION_INFO_USUARIO);
                        if (sessionWrapper != null) {
                            Logger.getLogger(SesionUtil.class.getName()).log(Level.INFO, "*****Usuario: {0}", sessionWrapper.getUsuario());
                            if (sessionWrapper.getUsuario() == null) {
                                sessionWrapper.setUsuario("<anonymous>");
                            }
                            Logger.getLogger(SesionUtil.class.getName()).log(Level.INFO, "USUARIO QUE SE CONECTA {0}", sessionWrapper.getUsuario());
                            sessionWrapper.setTiempoUltimaSolicitud(lastRequestTime);
                            listaSesionesAbiertas.add(sessionWrapper);
                        }
                    }
                }
            }
            return listaSesionesAbiertas;
        } catch (IllegalAccessException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (IllegalArgumentException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (InvocationTargetException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (NoSuchMethodException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (SecurityException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (ClassNotFoundException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        }
        return listaSesionesAbiertas;
    }    
    
    
    /**
     * Metodo encargado de verificar que el acceso a la aplicacion sea exitoso,
     * verificado que no se repitan sesion de un mismo usuario en diferentes
     * computadoras o dispositivos
     *
     * @param facesContext Recibe el contexto de la aplicacion actual.
     * @param current Recibe las credenciales del usuario que esta haciendo la
     * identificacion en la aplicacion
     * @return Retorna {@code EstadosLogin.EXITOSO} si no se encontraron errores
     * y {@code EstadosLogin.WARN_SESSION_ACTIVA} si el usuario ya se encuentra
     * identificado en otra computadora o dispositivo
     * @throws Exception Lanza la {@code Exception} si ocurre un error no
     * controlado
     */
    public static EstadoSesion accesoSolicitud(FacesContext facesContext, InformacionUsuario current) throws Exception {
        List<SessionWrapper> listaUsuarioSession = obtenerSesionesAbiertas(facesContext);
        for (SessionWrapper usuario : listaUsuarioSession) {
            if (usuario.getUsuario().equals(current.getUsuario()) && usuario.isAutentificado()) {
//                SesionUtil.recobrarSesionRemota(FacesContext.getCurrentInstance(), current.getUsuario());
                return EstadoSesion.WARN_SESSION_ACTIVA;//Ya existe una sesion activa de este usuario
            }
        }
        return EstadoSesion.EXITOSA;//Exitosa
    }    
    
    
 public static String obtenerBrowser(FacesContext facesContext) {
        String browser;
        String userAgent;
        userAgent = ((HttpServletRequest) facesContext.getExternalContext().getRequest()).getHeader("User-Agent");
        if (userAgent.contains("Chrome")) {
            browser = "Chrome";
        } else {
            if (userAgent.contains("Firefox")) {
                browser = "Firefox";
            } else {
                if (userAgent.contains("MSIE")) {
                    browser = "Internet Explorer";
                } else {
                    if (userAgent.contains("Opera")) {
                        browser = "Opera";
                    } else {
                        if (userAgent.contains("Safari")) {
                            browser = "Safari";
                        } else {
                            browser = "Desconocido";
                        }
                    }
                }
            }
        }
        return browser;
    }
 
     /**
     * Metodo que se encarga de invalidar la sesion del usuario actual y de
     * redireccionar al usuario
     *
     * @param facesContext
     * @throws Exception
     */
    public static void salirAplicacionForce(FacesContext facesContext, InformacionUsuario informacionUsuario) throws Exception {
        killSessionRFLCT(FacesContext.getCurrentInstance(), informacionUsuario);
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().clear();
        //facesContext.getExternalContext().redirect(facesContext.getExternalContext().getRequestContextPath() + REDIRECT);
    }
    
    
    /**
     * Se encarga de matar la session abierta del usuario.
     *
     * @param facesContext FacesContext de la aplicacion
     * @param identidad Usuario al que hay que sacar de session
     */
    public static void killSessionRFLCT(FacesContext facesContext, InformacionUsuario informacionUsuario) {
        try {
            Class memorySessionData = Class.forName(MEMORY_SESSION_DATA);
            Class memorySessionContext = Class.forName(MEMORY_SESSION_CONTEXT);

            SessionWrapper sessionWrapper;

            List<Object> killSessionsList = new ArrayList<Object>();

            HttpSession httpSession = (HttpSession) facesContext.getExternalContext().getSession(true);
            Method getOpenSessions = memorySessionContext.getMethod("getOpenSessions");
            Hashtable<String, Object> sessionAbiertas = (Hashtable<String, Object>) getOpenSessions.invoke(httpSession.getSessionContext());

            Iterator it = sessionAbiertas.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry ent = (Map.Entry) it.next();

                if (memorySessionData.isInstance(ent.getValue())) {
                    Method getAttribute = memorySessionData.getMethod("getAttribute", String.class);
                    if (getAttribute.invoke(ent.getValue(), ATRIBUTO_SESION_INFO_USUARIO) instanceof InformacionUsuario) {
                        sessionWrapper = (SessionWrapper) getAttribute.invoke(ent.getValue(), ATRIBUTO_SESION_INFO_USUARIO);
                        if (sessionWrapper != null) {
                            if (sessionWrapper.getUsuario().toUpperCase(Locale.getDefault()).equals(informacionUsuario.getId_usuario().toUpperCase(Locale.getDefault()))                                    
                                    && sessionWrapper.getBrowser().toUpperCase(Locale.getDefault()).equals(informacionUsuario.getBrowser().toUpperCase(Locale.getDefault()))) {
                                killSessionsList.add(ent.getValue());
                                break;
                            }
                        }
                    }
                }
            }
            for (int i = 0; i < killSessionsList.size(); i++) {
                Object sessionOpen = killSessionsList.get(i);
                Method invalidate = memorySessionData.getMethod("invalidate");
                limpiarSession((HttpSession) sessionOpen);
                invalidate.invoke(sessionOpen);
            }
        } catch (IllegalAccessException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (IllegalArgumentException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (InvocationTargetException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (NoSuchMethodException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (SecurityException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        } catch (ClassNotFoundException ex) {
            ExcepcionManager.manejarExcepcion(ex);
        }
    }
    
    
        private static void limpiarSession(HttpSession httpSession) {
        String elemento;
        List<String> listaElementos = new ArrayList<String>();
        for (Enumeration<String> e = httpSession.getAttributeNames(); e.hasMoreElements();) {
            elemento = e.nextElement();
            if (elemento.contains(ATRIBUTO_SESION_INFO_USUARIO)) {
                listaElementos.add(elemento);
            }
            if (elemento.contains(ATRIBUTO_SESION_IDENTIDAD_CLASS)) {
                listaElementos.add(elemento);
            }
        }
        for (String elementoEliminar : listaElementos) {
            httpSession.removeAttribute(elementoEliminar);
        }
    }

}
